package model.entity.container;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import model.entity.Entity;
import model.entity.Item;
import model.entity.Product;
import model.exceptions.InvalidNameException;

import common.Result;

//@formatter:off
/**
 * Describes a product container, with methods allowing
 * addition and removal of <code>ProductGroup</code>s.
 * 
 * @author Ryan Cheatham
 * 
 * {@invariant getName() != null}
 * {@invariant getName().length() > 0}
 */
//@formatter:on
public abstract class ProductContainer extends Entity
{
  private static final long    serialVersionUID = -889994870870805724L;

  /** The list of groups this product container holds directly (not through child product groups) */
  protected List<ProductGroup> productGroups    = new ArrayList<ProductGroup>();

  /** The name of this product container. */
  protected String             name;

  /** The list of items this product container holds directly (not through child product groups) */
  protected Set<Item>          items;

  /**
   * The list of products this product container holds directly (not through child product groups)
   */
  protected Set<Product>       products;

  //@formatter:off
  /**
   * Constructs a new product container with the given name.
   * 
   * @param name The name of the product container
   * 
   * {@pre name != null}
   * {@pre name.length() > 0}
   * 
   * {@post getName() == name}
 * @throws InvalidNameException 
   */
  //@formatter:on
  public ProductContainer(String name) throws InvalidNameException
  {
    if (!isValidName(name))
    {
      throw new InvalidNameException("Name must contain at least one character.");
    }

    this.name = name;
    items = new HashSet<Item>();
    products = new HashSet<Product>();
  }

  /**
   * Returns the name of this product container
   */
  public String getName()
  {
    return name;
  }

  //@formatter:off
  /**
   * Sets the name of this product container to the given name.
   * 
   * @param name
   *          The new name.
   * 
   * {@pre name != null}
   * {@pre name.length() > 0}
   * 
   * {@post getName() == name}
 * @throws InvalidNameException 
   */
  //@formatter:on
  public void setName(String name) throws InvalidNameException
  {
    if (!isValidName(name))
    {
      throw new InvalidNameException("Name must contain at least one character.");
    }

    this.name = name;
  }

  /**
   * Returns an iterator through all the items in this container.
   */
  public Iterator<Item> getItems()
  {
    return Collections.unmodifiableSet(items).iterator();
  }

  /**
   * Returns an iterator through all the product groups in this container.
   */
  public Iterator<ProductGroup> getProductGroups()
  {
    return Collections.unmodifiableList(productGroups).iterator();
  }

  /**
   * Validates whether the given product group can be added to this product container.
   * 
   * @param group
   *          The product group to validate.
   * @return a result, whose status indicates whether the given product group can be added to this
   *         container.
   * 
   *         {@pre group != null}
   */
  public Result validateAddProductGroup(ProductGroup group)
  {
    if (group == null)
    {
      return new Result(false, "Error: NULL group.");
    }
    if (productGroups.contains(group))
    {
      return new Result(false, "Product Group is already in the group.");
    }

    return new Result(true);
  }

  //@formatter:off
  /**
   * Validates whether the given product group can be added to this product container.
   * If so, the group is added to this container.
   * 
   * @param group
   *          The group to be added to this container.
   * @return a result, indicating whether this operation was successful or not.
   * 
   * {@pre group != null}
   * 
   * {@post the group is contained in this container}
   */
  //@formatter:on
  public Result addProductGroup(ProductGroup group)
  {
    Result canAdd = validateAddProductGroup(group);
    if (!canAdd.getStatus())
    {
      return canAdd;
    }

    productGroups.add(group);

    return new Result(true);
  }

  /**
   * Validates whether the given product group can be added to this product container.
   * 
   * @param group
   *          The product group to validate.
   * @return a result, whose status indicates whether the given product group can be removed from
   *         this container.
   * 
   *         {@pre group != null}
   */
  public Result validateRemoveProductGroup(ProductGroup group)
  {
    if (group == null)
    {
      return new Result(false, "Error: NULL group.");
    }
    if (!productGroups.contains(group))
    {
      return new Result(false, "Product Group is not in the group.");
    }

    return new Result(true);
  }

  /**
   * Attempts to remove the given product group from this container.
   * 
   * @param group
   *          The product group to remove.
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@post this group no longer contains the given group}
   */
  public Result removeProductGroup(ProductGroup group)
  {
    Result validRemove = validateRemoveProductGroup(group);
    if (!validRemove.getStatus())
    {
      return validRemove;
    }

    productGroups.remove(group);

    return new Result(true);
  }

  /**
   * Validates whether the given item can be added directly to this product container.
   * 
   * @param item
   *          The item to be validated.
   * @return a result, indicating whether the given item can be added to this product container or
   *         not.
   * 
   *         {@pre item != null}
   */
  public Result validateAddItem(Item item)
  {
    if (item == null)
    {
      return new Result(false, "Error: NULL item.");
    }
    if (items.contains(item))
    {
      return new Result(false, "Product Group is already in the container.");
    }

    return new Result(true);
  }

  //@formatter:off
  /**
   * Attempts to add the given item to this product container.
   * 
   * @param item
   *          The item to be added.
   * @return a result, indicating whether the given item can be
   *        added to this product container or not.
   * 
   * {@pre item != null}
   * 
   * {@post this container contains the given item}
   */
  //@formatter:on
  public Result addItem(Item item)
  {
    Result validAdd = validateAddItem(item);
    if (!validAdd.getStatus())
    {
      return validAdd;
    }

    items.add(item);

    return new Result(true);
  }

  public Result validateRemoveItem(Item item)
  {
    if (item == null)
    {
      return new Result(false, "Error: NULL item.");
    }
    if (!items.contains(item))
    {
      return new Result(false, "Product Group is not in the container.");
    }

    return new Result(true);
  }

  /**
   * Attempts to remove the given item from this product container. If the item is not directly in
   * this product container (instead of a child product container) then this method fails.
   * 
   * @param item
   *          The item to remove.
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@post this container no longer contains the given item}
   */
  public Result removeItem(Item item)
  {
    Result validRemove = validateRemoveItem(item);
    if (!validRemove.getStatus())
    {
      return validRemove;
    }

    items.remove(item);

    return new Result(true);
  }

  /**
   * Validates whether the given product can be added directly to this product container.
   * Specifically, this method checks to see if this product is held by any child containers. If
   * so, then the product cannot be added to this container directly.
   * 
   * @param product
   *          The product to validate.
   * @return a result, indicating whether the given product can be added directly to this product
   *         container.
   * 
   *         {@pre product != null}
   */
  public Result validateAddProduct(Product product)
  {
    if (product == null)
    {
      return new Result(false, "Error: NULL product.");
    }
    if (products.contains(product))
    {
      return new Result(false, "Product is not in the container.");
    }

    return new Result(true);
  }

  //@formatter:off
  /**
   * Attempts to add the given product directly to this product container.
   * 
   * @param product
   *          The product to add.
   * @return a result, indicating whether this operation was successful or not.
   * 
   * {@pre product != null}
   * 
   * {@post this container contains the given product}
   */
  //@formatter:on
  public Result addProduct(Product product)
  {
    Result canAdd = validateAddProduct(product);

    if (!canAdd.getStatus())
    {
      return canAdd;
    }

    products.add(product);

    return new Result(true);
  }

  public Result validateRemoveProduct(Product product)
  {
    if (product == null)
    {
      return new Result(false, "Error: NULL product.");
    }
    if (products.contains(product))
    {
      return new Result(false, "Product is not in the container.");
    }

    return new Result(true);
  }

  //@formatter:off
  /**
   * Attempts to remove the given product from this product container specifically.
   * If this product container does not contain the product,
   * but a child container does, then the product is not removed.
   * 
   * @param product
   *          The product to remove.
   * @return a result, indicating whether this operation was successful or not.
   * 
   * {@pre product != null}
   * 
   * {@post this container no longer contains the given product}
   */
  //@formatter:on
  public Result removeProduct(Product product)
  {
    Result validRemove = validateRemoveProduct(product);
    if (!validRemove.getStatus())
    {
      return validRemove;
    }

    items.remove(product);

    return new Result(true);
  }

  public static boolean isValidName(String name)
  {
    return name != null && name.length() > 0;
  }

  @Override
  public boolean equals(Object obj)
  {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof ProductContainer))
      return false;
    ProductContainer other = (ProductContainer) obj;
    if (name == null)
    {
      if (other.name != null)
        return false;
    }
    else if (!name.equals(other.name))
    {
      return false;
    }
    return true;
  }

}
